﻿#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
// 动态内存管理

// 1.动态内存分配的意义

// 在C语言中我们常常写到这样的代码
#if 0
int arr[10] = { 0 };
int a = 10;
#endif
 /*这样开辟空间的大小是固定的，并且数组一旦声明后，必须指定长度
 这意味着数组的空间大小一旦确定就无法更改

 为了解决这个问题，C语言中引入了动态内存管理，它可以让我们自由的分配和释放空间。*/


// 2. malloc 函数
 ///*头文件 - stdlib.h
 //作用：C语言中一个可以开辟动态内存的函数*/

 //函数原型：
 //void* malloc(size_t size);

 //size_t size - 需要开辟空间的大小
 //void*       - 返回指向开辟空间的指针 

 //开辟成功，返回指向开辟空间的指针 
 //开辟失败，返回NULL指针


// 3. free 函数
 //头文件 - stdlib.h
 //作用：专门用来给动态内存释放和回收的

 //函数原型：
 //void free (void* ptr);

 //void* ptr - 传入指向需要释放的动态空间的指针

 //如果参数 ptr 指向的空间不是动态开辟的，那free函数的⾏为是未定义的。
 //如果参数 ptr 是NULL指针，则函数什么事都不做。

#if 0
#include <stdlib.h>
int main()
{
	int num = 0;
	scanf("%d", &num);
	int arr[num] = { 0 };
	int* ptr = (int*)malloc(num * sizeof(int));
	if (NULL != ptr)
	{
		int i = 0;
		for (i = 0; i < num; i++)
		{
			*(ptr + i) = 0;
		}
	}
	free(ptr);
	ptr = NULL;
	return 0;
}
int main()
{
	int arr[5] = { 0 };
	int* ptr = (int*)malloc(5 * sizeof(int));
	if (NULL != ptr)
	{
		int i = 0;
		for (i = 0; i < 5; i++)
		{
			*(ptr + i) = i + 1;
		}

		for (int j = 0; j < 5; j++)
		{
			printf("%d ", arr[j]);
		}
	}
	free(ptr);
	ptr = NULL;
	return 0;
}
#endif


// 4.calloc 函数
 //头文件 - stdlib.h
 //作用：初始化并且开辟空间

 //函数原型：
 //void* calloc (size_t num, size_t size);

 //size_t num - 要分配的元素个数
 //size_t size - 要分配的元素大小

#if 0
int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (NULL != p)
	{
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			printf("%d ", *(p + i));
		}
	}
	
	free(p);
	p = NULL;
	return 0;
}
#endif

// 5.realloc 函数
// 头文件 - stdlib.h
//作用：realloc 函数就可以做到对动态开辟内存⼤⼩的调整。
//
// 函数原型
// void* realloc(void* ptr, size_t size);
//
// void* ptr - 指向需要调整的地址
// size_t size - 调整后的大小
//返回值为调整之后的内存起始位置。
// 这个函数调整原内存空间⼤⼩的基础上，还会将原来内存中的数据移动到新的空间。
// realloc在调整内存空间的是存在两种情况：
//情况1：原有空间之后有⾜够⼤的空间
//情况2：原有空间之后没有⾜够⼤的空间
//
//情况1
//当是情况1的时候，要扩展内存就直接原有内存之后直接追加空间，原来空间的数据不发⽣变化。
//情况2
//当是情况2的时候，原有空间之后没有⾜够多的空间时，扩展的⽅法是：在堆空间上另找⼀个合适⼤⼩
//的连续空间来使⽤。这样函数返回的是⼀个新的内存地址

#if 0
#include <stdio.h>
#include <stdlib.h>
int main()
{
	int* ptr = (int*)malloc(100);
	if (NULL != ptr)
	{
		// .....
	}
	else
	{
		return 1;
	}

	//扩容
	int* p = realloc(ptr, 1000);
	if (NULL != p)
	{
		ptr = p;
	}

	free(ptr);
	ptr = NULL;
	p = NULL;
	return 0;
}
#endif


// 6.常见的动态内存错误

// 6.1 对NULL指针的解引⽤操作
#if 0
void test()
{
	int* p = (int*)malloc(INT_MAX / 4);
	*p = 20;//如果p的值是NULL，就会有问题
	free(p);
}



// 6.2 对动态开辟空间的越界访问

void test()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;//当i是10的时候越界访问
	}
	free(p);
	p = NULL;
}


// 6.3 对非动态开辟内存使⽤free释放
void test()
{
	int a = 10;
	int* p = &a;
	free(p);
}


// 6.4 使⽤free释放⼀块动态开辟内存的⼀部分
void test()
{
	int* p = (int*)malloc(100);
	p++;
	free(p);//p不再指向动态内存的起始位置
}


// 6.5 对同⼀块动态内存多次释放
void test()
{
	int* p = (int*)malloc(100);
	free(p);
	free(p);//重复释放
}

// 6.6 动态开辟内存忘记释放（内存泄漏）
void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
}
}
int main()
{
	test();
	while (1);
}
#endif

// 7. 动态内存笔试题
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


// 题⽬1：

#if 0
void GetMemory(char* p) 
{
	p = (char*)malloc(100); // p 将会在出GetMemory后销毁，生命周期结束
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str); // 传值调用，无法对str进行修改，str还是指向NULL
	strcpy(str, "hello world");
	printf(str);
}

int main()
{
	GetMemory();
	return 0;
}


//题⽬2：
char* GetMemory(void)
{
	char p[] = "hello world";
	return p; // p是一个指针，指向p[]的首元素地址，但是p[]在出了函数会销毁
}
void Test(void)
{
	char* str = NULL;
	str = GetMemory(); // str 接受的 p指针是一个野指针
	printf(str);
}


//题⽬3：
void GetMemory(char** p, int num) //char** - 接收 char*
{
	*p = (char*)malloc(num);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100); // 传址调用
	strcpy(str, "hello"); //整个代码逻辑没有问题，缺点是会出现内存泄漏，需要补上
	printf(str);

	//补上：
	free(str);
	str = NULL;
}

//题⽬4：
void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str); // 这里释放str，后面的调用就失效了
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}
#endif


// 8. 柔性数组

// 8.1 什么是柔性数组
#if 0
typedef struct S
{
	int i;
	int a[0];// 柔性数组
}s;
#endif
// 柔性数组 - 指的就是结构体中最后一个成员是未知大小的数组

// 8.2 柔性数组的特点
//1.结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
//2.sizeof返回的这种结构⼤⼩不包括柔性数组的内存。
//3.包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配，并且分配的内存应该⼤于结构的⼤⼩，以适应柔性数组的预期⼤⼩。
#if 0
typedef struct S
{
	int i;
	int a[0];// 柔性数组
}s;

int main()
{
	printf("%zd\n", sizeof(s));
	return 0;
}



// 8.3 柔性数组的使用

typedef struct S
{
	int i;
	int a[0];// 柔性数组
}s;

int main()
{
	int i = 0;
	s* p = (s*)malloc(sizeof(s) + 100 * sizeof(int));

	p->i = 100;
	for (i = 0; i < 100; i++)
	{
		p->a[i] = i;
	}
	free(p);
	p = NULL;
	return 0;
}
#endif